<?php
namespace App\Repositories;

use Exception;
use Illuminate\Support\Collection;
use App\Exceptions\OrdinaryException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

/**
 * @date 2018-11-12
 * @author zx
 * @package App\Repositories
 */
abstract class BaseRepository implements RepositoryContract {

    /**
     * 用于保存模型实例
     * @var Model
     * @date 2018-11-12
     * @author zx
     */
    protected $model;

    /**
     * 用于保存查询构造器实例
     * @var Builder
     * @date 2018-11-12
     * @author zx
     */
    protected $query;

    /**
     * 保存查询的limit
     * @var int
     * @date 2018-11-12
     * @author zx
     */
    protected $take;

    /**
     * 相关模型的数组
     * @var array
     * @date 2018-11-12
     */
    protected $with = [];

    /**
     * 一个或多个WHERE子句参数数组
     * @var array
     * @date 2018-11-12
     */
    protected $wheres = [];

    /**
     * 一个或多个WHEREIN子句参数数组
     * @var array
     */
    protected $whereIns = [];

    /**
     * 按列/值对排列的一个或多个数组
     * @var array
     * @date 2018-11-12
     */
    protected $orderBys = [];

    /**
     * 用于定义查询作用域
     * @var array
     * @date 2018-11-12
     * @author zx
     */
    protected $scopes = [];

    /**
     * BaseRepository Construct
     * @throws OrdinaryException
     */
    public function __construct()
    {
        $this->makeModel();
    }

    /**
     * 用于生成模型实例的抽象方法
     *
     * @date 2018-11-12
     * @author zx
     * @return mixed
     */
    abstract public function model();

    /**
     * 生成模型实例
     *
     * @date 2018-11-12
     * @author zx
     * @return Model|mixed
     * @throws OrdinaryException
     */
    public function makeModel()
    {
        $model = app()->make($this->model());

        if(!$model instanceof Model) {
            throw new OrdinaryException("CLASS {$this->model()} must be an instance of ".Model::class, 500);
        }

        return $this->model = $model;
    }

    /**
     * 获取模型所有资源
     *
     * @date 2018-11-12
     * @author zx
     * @param array $columns
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function all(array $columns=["*"])
    {
        $this->newQuery()->eagerLoad();

        $models = $this->query->get($columns);

        $this->unsetClauses();

        return $models;
    }

    /**
     * 获取集合的总汇
     *
     * @date 2018-11-14
     * @author zx
     * @return int
     */
    public function count():int
    {
        return $this->get()->count();
    }

    /**
     * 创建一个模型资源
     *
     * @param array $attributes
     * @date 2018-11-14
     * @author zx
     * @return $this|Model|mixed
     * @throws OrdinaryException
     */
    public function create(array $attributes)
    {
        $this->unsetClauses();
        try{
            return $this->model->create($attributes);
        }catch (Exception $exception) {
            throw new OrdinaryException("未知的错误导致模型创建失败", 500);
        }
    }

    /**
     * 创建一条或多条模型数据到数据库
     *
     * @param array $collections
     * @date 2018-11-14
     * @author zx
     * @return Collection|mixed
     * @throws OrdinaryException
     */
    public function createMultiple(array $collections)
    {
        // TODO: Implement createMultiple() method.
        $models = new Collection();
        foreach($collections as $collection){
            $models->push($this->create($collection));
        }
        return $models;
    }

    /**
     * 销毁指定的模型资源
     *
     * @date 2018-11-14
     * @param Model $model
     * @param bool $force
     * @author zx
     * @return bool|null
     * @throws OrdinaryException
     */
    public function destroy(Model $model, bool $force = FALSE)
    {
        // TODO: Implement destroy() method.
        try{
            return $force ? $model->forceDelete() : $model->delete();
        } catch (Exception $exception) {
            throw new OrdinaryException("未知的错误导致删除失败!", 500);
        }
    }

    /**
     * 删除模型资源
     *
     * @date 2018-11-14
     * @author zx
     * @return bool
     */
    public function delete():bool
    {
        $this->newQuery()->setClauses()->setScopes();
        $result = $this->query->delete();
        $this->unsetClauses();
        return $result;
    }

    /**
     * 根据主键删除对应资源
     *
     * @param $primary
     * @date 2018-11-14
     * @author zx
     * @return bool
     * @throws OrdinaryException
     */
    public function deleteByPrimary($primary):bool
    {
        // TODO: Implement deleteByPrimary() method.
        $this->unsetClauses();
        $instance = $this->getByPrimary($primary);
        try{
            return $instance->delete();
        } catch (Exception $exception) {
            throw  new OrdinaryException("未知的错误导致删除失败", 500);
        }
    }

    /**
     * 根据主键删除一组资源
     *
     * @param array $primaries
     * @date 2018-11-14
     * @author zx
     * @return int
     */
    public function deleteMultipleByPrimary(array $primaries):int
    {
        // TODO: Implement deleteMultipleByPrimary() method.
        return $this->model->destroy($primaries);
    }

    /**
     * 根据主键获取对应的模型实例
     *
     * @param $primary 主键
     * @param array $columns
     * @date 2018-11-14
     * @author zx
     * @return mixed|Model|NULL|Object|static
     * @throws OrdinaryException
     */
    public function getByPrimary($primary, array $columns = ["*"])
    {
        // TODO: Implement getByPrimary() method.
        $this->unsetClauses();
        $this->newQuery()->eagerLoad();
        $result = $this->query->where($this->model->getKeyName(), $primary)->first($columns);
        if ($result) return $result;
        throw new OrdinaryException('资源不存在', 404);
    }

    /**
     * 获取第一条模型资源
     *
     * @param array $columns
     * @date 2018-11-14
     * @author zx
     * @return Model|static
     */
    public function first(array $columns = ["*"])
    {
        // TODO: Implement first() method.
        $this->newQuery()->eagerLoad()->setClauses()->setScopes();
        $model = $this->query->firstOrFail($columns);
        $this->unsetClauses();
        return $model;
    }

    /**
     * 获取模型数据集合
     *
     * @param array $columns
     * @date 2018-11-13
     * @author zx
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function get(array $columns=['*'])
    {
        $this->newQuery()->eagerLoad()->setClauses()->setScopes();

        $models = $this->query->get($columns);

        $this->unsetClauses();

        return $models;
    }

    /**
     * 获取指定列
     *
     * @param $item
     * @param $column
     * @param array $columns
     * @date 2018-11-14
     * @author zx
     * @return Model|NULL|static
     */
    public function getByColumn($item, $column, array $columns = ["*"])
    {
        // TODO: Implement getByColumn() method.
        $this->unsetClauses();
        $this->newQuery()->eagerLoad();
        return $this->query->where($column, $item)->first($columns);
    }

    /**
     * 设置分页
     *
     * @date 2018-11-14
     * @author zx
     * @param int $limit 条数
     * @param array $columns
     * @param string $pageName
     * @param null $page
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function paginate(int $limit = 25, array $columns = ["*"], string $pageName = 'page', $page = NULL)
    {
        // TODO: Implement paginate() method.
        $this->newQuery()->eagerLoad()->setClauses()->setScopes();
        $models = $this->query->paginate($limit, $columns, $pageName, $page);
        $this->unsetClauses();
        return $models;
    }

    /**
     * 根据主键更新资源
     *
     * @param $primary
     * @param array $attributes
     * @param array $options
     * @date 2018-11-14
     * @author zx
     * @return BaseRepository|Model|mixed|NULL|object
     * @throws OrdinaryException
     */
    public function updateByPrimary($primary, array $attributes, array $options = [])
    {
        // TODO: Implement updateByPrimary() method.
        $this->unsetClauses();
        $model = $this->getByPrimary($primary);
        try{
            $model->update($attributes, $options);
            return $model;
        } catch (Exception $exception) {
            throw new OrdinaryException("未知错误导致更新失败", 500);
        }
    }

    /**
     * 设置查询条数
     *
     * @param int $limit
     * @author zx
     * @date 2018-11-14
     * return $this
     */
    public function limit(int $limit)
    {
        // TODO: Implement limit() method.
        $this->take = $limit;
        return $this;
    }

    /**
     * 设置排序方式
     *
     * @param $column
     * @param string $direction
     * @date 2018-11-14
     * @author zx
     * return $this
     */
    public function orderBy(string $column, string $direction="asc")
    {
        // TODO: Implement orderBy() method.
        $this->orderBys[] = compact("column", "direction");
        return $this;
    }

    /**
     * 设置WHERE查询条件
     *
     * @param $column
     * @param string $operator
     * @param $value
     * @date 2018-11-14
     * @author zx
     * @return $this
     */
    public function where($column, $value, string $operator = "=")
    {
        // TODO: Implement where() method.
        $this->wheres[] = compact("column", "operator", "value");
        return $this;
    }

    /**
     * 设置WHEREIN查询条件
     *
     * @param $column
     * @param $values
     * @date 2018-11-14
     * @author zx
     * @return $this
     */
    public function whereIn($column, $value)
    {
        // TODO: Implement whereIn() method.
        $values = is_array($value) ? $value : [$value];
        $this->whereIns[] = compact("column", "values");
        return $this;
    }

    /**
     * 生成查询语句
     *
     * @date 2018-11-13
     * @author zx
     * @return $this
     */
    protected function setClauses()
    {
        foreach ($this->wheres as $where) {
            $this->query->where($where['column'], $where['operator']?:$where['value'], $where['operator']?$where['value']:NULL);
        }
        foreach ($this->whereIns as $whereIn) {
            $this->query->whereIn($whereIn['column'], $whereIn['values']);
        }
        foreach ($this->orderBys as $orders) {
            $this->query->orderBy($orders['column'], $orders['direction']);
        }
        if (isset($this->take) and ! is_null($this->take)) $this->query->take($this->take);

        return $this;
    }

    /**
     * 预加载包
     * @date 2018-11-12
     * @author zx
     * @param $relations
     * @return $this
     */
    public function with($relations)
    {
        if(is_string($relations)){
            $relations = func_get_args();
        }
        $this->with = $relations;
        return $this;
    }

    /**
     * 加载多个关联关系
     * @date 2018-11-12
     * @author zx
     * @return $this
     */
    protected function eagerLoad()
    {
        foreach($this->with as $relation){
            $this->query->with($relation);
        }
        return $this;
    }

    /**
     * 生成Query Builder
     * @date 2018-11-12
     * @author zx
     * @return $this
     */
    protected function newQuery()
    {
        $this->query = $this->model->newQuery();

        return $this;
    }

    /**
     * 设置查询作用域
     * @date 2018-11-13
     * @author zx
     * @return $this
     */
    protected function setScopes()
    {
        foreach ($this->scopes as $method => $args) {
            $this->query->$method(implode(',', $args));
        }
        return $this;
    }

    /**
     * 重置查询参数
     *
     * @date 2018-11-14
     * @author zx
     * @return $this
     */
    protected function unsetClauses()
    {
        $this->wheres = [];
        $this->whereIns = [];
        $this->scopes = [];
        $this->take = NULL;

        return $this;
    }

    /**
     * 序列化属性为模型
     *
     * @param array $attributes
     * @date 2018-11-14
     * @author zx
     * @return Model
     */
    abstract protected function serialization(array $attributes);
}